In [1]:
import pySPM
print(pySPM.__version__)
Let's set some variable and libraries to retrieve the data and plot them
In [2]:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
import os
from IPython import display
Let's import the pySPM_data library which provides all example data files. You can download it here https://github.com/scholi/pySPM_data in case you whish to run this Documentation script with jupyter
In [3]:
from pySPM_data import get_data
In [4]:
filename = get_data("Circles_defects.xml")
scan = pySPM.Nanoscan(filename)
scan.list_channels()
In [5]:
topo = scan.get_channel() # is equivalent to scan.get_channel('Topography',backward=False)
A1st = scan.get_channel("A_1st")
print(scan.get_summary())
fig, ax = plt.subplots(1, 2, figsize=(14,7))
topo.show(ax=ax[0])
A1st.show(ax=ax[1], sig=2);
In [6]:
filename = get_data("Smileys.001")
ScanB = pySPM.Bruker(filename)
ScanB.list_channels()
In [7]:
topoB = ScanB.get_channel()
adhesion = ScanB.get_channel("Adhesion")
deformation = ScanB.get_channel("Deformation")
fig, ax = plt.subplots(1, 3, figsize=(21, 7))
topoB.show(ax=ax[0])
adhesion.show(ax=ax[1])
deformation.show(ax=ax[2], cmap='jet', sig=2);
Bruker tends to save information only on the forward either backward scans. To simplify your life, the get_channel has an argument lazy=True which will switch automatically to forward/backward if no image is found for the backard/forward channel. If you wish to get absolutely the forward/backward channel and get an error if not found, please set lazy=False.
In the example below, the error raised demonstrate that the topography channel is only available for the backward scan!
In [8]:
try:
ScanB.get_channel(lazy=False)
except Exception as e:
display.display(display.HTML("<b style='color: red'>{}</b>: {}".format(type(e).__name__, e.args[0])))
In [9]:
filename = get_data("test.sxm")
S = pySPM.SXM(filename)
S.list_channels()
fig, ax = plt.subplots(1,2,figsize=(14,7))
S.get_channel('Z').show(ax=ax[0]);
p = S.get_channel('Current').show(ax=ax[1], cmap='viridis');
In [10]:
topoB.pixels
Out[10]:
In [11]:
fig, ax = plt.subplots(1, 2, figsize=(14, 7))
ax[0].imshow(topoB.pixels, cmap='hot');
topoB.show(ax=ax[1], pixels=True, flip=True);
In [12]:
topo2 = topoB.correct_lines(inline=False)
topo3 = topoB.correct_plane(inline=False)
fig, ax = plt.subplots(1,3,figsize=(21, 7))
topoB.show(ax=ax[0])
ax[0].set_title("Original image")
topo2.show(ax=ax[1])
ax[1].set_title("Corrected by lines")
topo3.show(ax=ax[2])
ax[2].set_title("Corrected by slope");
In [13]:
fig = plt.figure(figsize=(7,7))
import copy
topo4 = copy.deepcopy(topoB) # make deepcopy of object otherwise you will just change the original
topo4.correct_median_diff()
topo4.show();
In [14]:
fig, ax = plt.subplots(1, 2, figsize=(14, 7))
topoC = topo4.filter_scars_removal(.7,inline=False)
topo4.show(ax=ax[0]);
topoC.show(ax=ax[1]);
for a in ax:
for p in [(71,50),(10,13),(32,13),(80,5)]:
a.annotate("", p, (p[0]+5, p[1]+5), arrowprops=dict(arrowstyle="->", color='w'));
In [15]:
from skimage.morphology import binary_erosion, disk
topoD = topoC.corr_fit2d(inline=False).offset([[10, 0, 10, 511]]).filter_scars_removal()
mask0 = topoD.get_bin_threshold(.1, high=False)
mask1 = binary_erosion(mask0, disk(3))
topoD2 = topoC.corr_fit2d(mask=mask1, inline=False).offset([[10, 0, 10, 511]]).filter_scars_removal().zero_min()
In [16]:
fig, ax = plt.subplots(2, 3, figsize=(21, 14))
ax = np.ravel(ax)
topoC.show(ax=ax[0])
ax[0].set_title("Image: median_diff correction")
topoD.show(ax=ax[1], sig=1.5);
ax[1].set_title("Polynomial correction on whole image")
ax[2].imshow(mask0);
ax[2].set_title("Mask: binary thersholding")
ax[3].imshow(mask1);
ax[3].set_title("Mask: background selection");
topoD2.show(ax=ax[4], pixels=True);
ax[4].set_title("Polynomial correction on background mask");
topoD2.show(ax=ax[5], adaptive=True);
A better way to correct the data is by defining pathes that are known to be flat and each line will be offseted automatically in order to get the defines lines as flat as possible. First let's display the data with the pixel values in the axis instead of the real size. The pixels coordinated will be used to define the path.
In [17]:
fig, ax = plt.subplots(1, 2, figsize=(14, 7))
topoD = topoB.filter_scars_removal(inline=False).offset([
[10,0,10,511],
[70,0,170,100],
[240,240,160,340]], ax=ax[0], width=10, alpha=.2, axPixels=True)
topoB.show(ax=ax[0], pixels=True)
topoD.show(ax=ax[1], pixels=True);
In [18]:
fig, (ax, ax2) = plt.subplots(2, 3, figsize=(21, 14))
topoD.show(ax=ax[0], cmap='gray', title="color map=\"gray\"")
topoD.show(ax=ax[1], sig=2, title="standard deviation=2")
topoD.show(ax=ax[2], adaptive=True, title="Adaptive colormap")
topoD.show(ax=ax2[0], dmin=4e-8, cmap='gray', title="raise the lowest value for the colormap of +40nm")
topoD.show(ax=ax2[1], dmin=3e-8, dmax=-3e-8, cmap='gray',title="raise lower of +30nm and highest of -30nm")
topoD.show(ax=ax2[2], pixels=True, title="Set axis value in pixels");
In [19]:
fig, ax = plt.subplots(1,2,figsize=(10,5))
topoD2.plot_profile(280,250,400,208,ax=ax[1],img=ax[0])
topoD2.show(ax=ax[0],pixels=True)
pySPM.utils.Ydist(plt.gca(), .14, 1.22, 27, unit="nm")
plt.tight_layout()
In [20]:
fig, ax = plt.subplots(1,3,figsize=(21,5))
topoD2.show(ax=ax[0])
topoD2.plot_profile(50, 47, 75, 57, ax=ax[1], img=ax[0], pixels=False);
topoD2.plot_profile(50, 47, 75, 57, ax=ax[2], pixels=False);
ax[2].set_title("ztransf can be used to adj. the z-scale.\n Here in nm with the minimum at 0");
ax[2].grid();
ax[2].yaxis.set_minor_locator(mpl.ticker.AutoMinorLocator()) # set minor ticks every units
ax[2].xaxis.set_minor_locator(mpl.ticker.AutoMinorLocator()) # set minor ticks automatically
ax[2].grid(axis='both',which='minor',alpha=.2); # display the minor grid every nm vertically
plt.tight_layout();
In [21]:
TOF = pySPM.ITA(get_data("BigSmiley.ita"))
TOF.show_masses()
In [22]:
fig, ax = plt.subplots(1,3,figsize=(21,7))
TOF.get_added_image_by_mass(19).show(cmap='hot',ax=ax[0])
img, ch = TOF.get_added_image_by_name('Au')
img.show(cmap='hot',ax=ax[1]);
img.show(cmap='hot',ax=ax[2], flip=True);
In [23]:
fig, ax = plt.subplots(1, 3, figsize=(21, 7))
# At least one Carbon
C, ch = TOF.get_added_image_by_name("C[^a-z]")
TOF.show_masses(ch)
# Two carbons Carbon
C2,_ = TOF.get_added_image_by_name("C_2")
FCl,_ = TOF.get_added_image_by_name(["Cl","F"])
C.show(ax=ax[0], wrap=26, flip=True); # wrap allows the title to be on multiple lines (its value is the number of chars per line)
C2.show(ax=ax[1], flip=True);
FCl.show(ax=ax[2], flip=True);
In [24]:
C = pySPM.Collection() # Create a collection of images
C['Au'] = TOF.get_added_image_by_mass(197)
img, ch = TOF.get_added_image_by_name('CH')
C['Carb'] = img
C['Cl'] = TOF.get_added_image_by_name('Cl')[0]
Ov,Ovs = C.overlay(['Carb','Au','Cl'],[[1,0,0],[0,1,0],[0,0,1]])
fig, ax = plt.subplots(1,4,figsize=(21,7))
Ov.show(ax=ax[0], flip=True)
for i,O in enumerate(Ovs, start=1):
O.show(ax=ax[i], flip=True);
In [25]:
fig, ax = plt.subplots(1, 3, figsize=(21, 7))
Au = TOF.get_added_image_by_mass([35,37])
Au.show(ax=ax[0], pixels=True);
Au.plot_profile(0, 450, 511, 40, ax=ax[1], img=ax[0], imgColor='c');
ax[2].imshow(TOF.get_xsection_by_mass(0, 450, 511, 40, [35, 37]));
In [26]:
Path = get_data("Cysteine_B3p_p_01_0.ita")
A = pySPM.ITA(Path)
sf, k0 = A.auto_mass_cal()
fig, ax = plt.subplots(4, 1,figsize=(21, 14))
A.show_spectrum(high=150, ax=ax[0], sf=sf, k0=k0)
#A.showSpectrum(low=56.5,high=57.5, ax=ax[0], sf=sf, k0=k0)
A.show_spectrum_around('C2H3NO', ax=ax[1], sf=sf, k0=k0);
# Add the mass C2H3NO to the mass calibration
sf, k0 = A.auto_mass_cal(fitting_peaks="C,CH,CH2,CH3,Na,C2H3NO", sf=sf, k0=k0)
A.show_spectrum_around('C2H3NO', ax=ax[2], sf=sf, k0=k0); # Shift the mass calibration to match the peaks
A.show_spectrum_around('C2H3NO', ax=ax[3], dofit=True, sf=sf, k0=k0); # Shift the mass calibration to match the peaks
In [27]:
TOFC = pySPM.ITA_collection(get_data("BigSmiley.ita"))
TOFC.show(ncols=4,wrap=30,flip=True)
plt.tight_layout();
In [28]:
fig, ax = plt.subplots(1, 5, figsize=(21,7))
TOFC['carbs'] = TOFC.ita.get_added_image_by_name("C[^a-z]")[0]
channels = ['carbs', 'Au-', 'Cl-']
O,ch = TOFC.overlay(channels, vmin=0, sig=4)
ax[2].set_title("Overlay: "+", ".join(channels))
O.show(ax=ax[3], flip=True)
TOFC['Au-'].add_scale(50e-6, height=10, ax=ax[3], loc=3);
for i,x in enumerate(ch):
x.show(ax=ax[i], flip=True);
pySPM.collection.overlay_triangle(channels, ax=ax[4], fontsize=16, proportion=.7)
In [29]:
TOFC.run_pca()
TOFC.PCA.screeplot()
In [30]:
TOFC.show_pca(num=3, flip=True)
TOFC.loadings(num=3)
Out[30]:
Here on top the negative loading are in blue and the positive in red (default). You can change the colormap (e.g. cmap='seismic') to get other contrasts. By default the colormap is symmetric so that the middle color equal to zero. I also like to get the laodings as a hinton map as follow:
In [31]:
fig = plt.figure(figsize=(21,2))
TOFC.PCA.hinton(matrix=TOFC.loadings(num=3))
Red are for the positive loadings and blue for the negative ones. The size of each square is proportional to their intensity.
If you wish to get two images per PC, one for the positive part and the other for the negative one, then use pn=True in showPCA:
In [32]:
TOFC.show_pca(num=3, flip=True, pn=True)
In [33]:
I = pySPM.ITM(get_data("BigSmiley.itm"))
I.show_values(names=['Instrument.LMIG.Aperture_1','Instrument.LMIG.Extractor','Instrument.LMIG.Suppressor','Instrument.LMIG.EmissionCurrent','Experiment.AcquisitionTime'])
You can also display them in a GUI in order to explore the data more conveniently:
In [34]:
#I.show_values(gui=True)
In [35]:
fig, ax = plt.subplots(1,1,figsize=(20,7))
axb = pySPM.utils.dual_plot(ax,'C0','C1') # create a dual plot with blue (C0) data/axis on the left and orange (C1) on the right
I.show_meas_data(ax=ax, mul=1e6, scans=3) # Display a tick every 3 scans (top axis)
I.show_meas_data("Instrument.LMIG.Suppressor",ax=axb, color='C1', scans=False);
ax.set_ylim((.8, 1.2));
axb.set_ylim((1168,1172));
In [36]:
fig, ax = plt.subplots(1, 3, figsize=(21, 7))
pixel_list, pixel_order, blocks = I.get_raw_data(scan=0)
A = np.array(pixel_list)
distances = np.sqrt(np.sum((A[1:, :]-A[:-1, :])**2, axis=1))
ax[0].imshow(pixel_order, cmap='hsv');
ax[0].set_title("colormap of the pixel order")
ax[1].hist(distances/pixel_order.shape[0], bins=100);
ax[1].set_title("Distribution of the distance between\ntwo consecutive pixel in random order")
ax[1].set_xlabel("Ratio of the distance with the width [px/px]");
ax[1].set_xlim((0, np.sqrt(2)));
cm = plt.get_cmap('hsv')
N = 30
for i in range(N):
ax[2].plot(A[i:i+2,0], A[i:i+2,1],'o-',color=cm(float(i)/N), linewidth=2);
ax[2].set_xlim((0,pixel_order.shape[1]))
ax[2].set_ylim((0,pixel_order.shape[0]));
ax[2].set_title("Order of the first {} pixels".format(N))
ax[2].set_xlabel("x-position [px]")
ax[2].set_ylabel("y-position [px]");
ax[2].set_aspect('equal');
In [37]:
from IPython import display
masses, raw_spectrum = I.get_raw_spectrum(scans=range(2), prog=True) # get raw spectrum of the first 10 scans
display.clear_output()
fig = plt.figure(figsize=(20,7))
plt.plot(masses, raw_spectrum);
In [38]:
masses = I.channel2mass(np.arange(masses.size))
fig, ax = plt.subplots(1, 4, figsize=(21, 7))
pySPM.utils.showPeak(masses, raw_spectrum, pySPM.utils.get_mass('CH3'), .25, ax=ax[0]);
pySPM.utils.showPeak(masses, raw_spectrum, pySPM.utils.get_mass('Cl'), .25, ax=ax[1]);
pySPM.utils.showPeak(masses, raw_spectrum, pySPM.utils.get_mass('Au'), .25, ax=ax[2], polarity='-');
pySPM.utils.showPeak(masses, raw_spectrum, pySPM.utils.get_mass('Au'), .25, ax=ax[3], include_only='Au');
The warning message can be easily ignored. It comes from the fact that the calibration values can be written at different places. When the values are missing in the first place, the the alternative place is used and a warning message is raised. You can suppress the warning as follow if needed:
In [39]:
import warnings
warnings.simplefilter("ignore")
sf, k0 = I.get_mass_cal()
warnings.simplefilter("always")
In [40]:
from tqdm import tqdm_notebook as tqdm
import cProfile
delta = .1
Na, Ag = I.reconstruct([(pySPM.utils.get_mass(x+'+')-delta, pySPM.utils.get_mass(x+'+')+delta) for x in ['Cl', 'Au']], scans=[0, 1], prog=True)
fig, ax = plt.subplots(1, 2, figsize=(15, 7))
Na.show(ax=ax[0]);
Ag.show(ax=ax[1]);
In [41]:
from shutil import copyfile
copyfile(get_data("BigSmiley.ita"), "temp.ita");
t = pySPM.ITA('temp.ita')
In order to open the file in readonly in order to avoid any editing by mistake, plese use the parameter readonly=True when you open a file
In [42]:
ro = pySPM.ITA("temp.ita", readonly=True)
# Let's look for the mass interval (mi) for the CH- channel
for x in ro.root.goto("MassIntervalList"):
if x.name != 'mi':
continue
if x.goto("assign").get_string()=="CH-":
break
# let's try to edit the desc value
print("desc: \"{}\"".format(x.goto("desc").get_string()))
try:
x.edit_block("", "desc", "test".encode("utf16")[2:], force=True)
except Exception as e:
display.display(display.HTML("<b style='color: red'>{}</b>: {}".format(type(e).__name__, e.args[0])))
print("desc: \"{}\"".format(x.goto("desc").get_string()))
But this would work fine with a writable file
In [43]:
# Let's look for the mass interval (mi) for the CH- channel
for x in t.root.goto("MassIntervalList"):
if x.name != 'mi':
continue
if x.goto("assign").get_string()=="CH-":
break
# let's try to edit the desc value
print("desc: \"{}\"".format(x.goto("desc").get_string()))
x.edit_block("", "desc", "test".encode("utf16")[2:], force=True)
print("desc: \"{}\"".format(x.goto("desc").get_string()))
In [44]:
img = 255*pySPM.utils.misc.smiley(512, ratio=.6, eye=.2, mouth_rad=.65, mouth_thick=.25, eye_sep=.27, eye_height=.4)
plt.imshow(img)
miblock = t.create_new_miblock("", desc="smiley", peaklabel=1)
t.add_new_images(miblock, Added=img);
In [45]:
t.get_added_image_by_name("smiley")[0].show(flip=True);
You can now open the file temp.ita with SurfaceLab and see a new channel called "smiley"
In [46]:
ax = pySPM.utils.sp(3) # create 3 sub-plots
x = np.arange(100)
ax[0].plot(x,pySPM.utils.Gauss(x,50,10,1), label='Gauss');
ax[0].plot(x,pySPM.utils.LG(x,50,10,1, lg=1), label='Lorentz')
ax[0].plot(x,pySPM.utils.LG(x,50,10,lg=.5), label='Lorentz-Gauss (50-50%)')
ax[0].legend();
ax[1].plot(x,pySPM.utils.CDF(x,30,5)-pySPM.utils.CDF(x,70,5), label="CDF")
ax[1].set_title("CDFs");
for i,g in enumerate([1,.5,.1]):
for j,nu in enumerate([1,2]):
ax[2].plot(x, pySPM.utils.logistic(x, x0=50, growth=g, nu=nu), label='growth={}, nu={}'.format(g, nu), color='C'+str(i+1), linestyle=['-','--',':'][j]);
ax[2].legend();
ax[2].set_title("Logistic function (can fit most of step functions)");
In [47]:
Y, X = np.mgrid[:500,:500]
R = np.sqrt((X-250)**2+(Y-250)**2)
LeftI= (np.sqrt((X-150)**2+(Y-150)**2)<50)
smiley = (R>=220)*(R<245)+LeftI+(np.sqrt((X-350)**2+(Y-150)**2)<50)+(R>100)*(R<150)*(Y>=250)
ax = pySPM.utils.sp(3)
ax[0].imshow(smiley);
pySPM.utils.plotMask(ax[0], LeftI, 'r'); # Let you paint a specific region by a specific color
x = np.arange(500)
ax[1].plot(x, pySPM.utils.Gauss(x,111,10)+pySPM.utils.Gauss(x,327,10));
pySPM.utils.Xdist(ax[1],111,327, 0.02); # plot and highlight distance
ax[2].plot(x, np.cos(x/10));
axb = pySPM.utils.DualPlot(ax[2]); # automatically create a dual plot and paint legend with correct colors
axb.plot(x, np.sin(x/10), 'C1');
In [48]:
TOF = pySPM.ITA(get_data("BigSmiley.ita"))
Au,_ = TOF.getAddedImageByName('Au')
pySPM.utils.save('PySPM-DOC',A=12.123)
pySPM.utils.save('PySPM-DOC',B=7,Au=Au)
ax = pySPM.utils.sp(2)
Au.show(ax=ax[0], flip=True);
Au2 = pySPM.utils.load('PySPM-DOC','Au')
Au2.show(ax=ax[1], flip=True);
print(pySPM.utils.load('PySPM-DOC','B'))
In [49]:
import datetime
print(datetime.datetime.now())